import * as THREE from 'three';
import {
  AmbientLight,
  AxesHelper,
  BufferGeometry,
  Clock,
  Color,
  DirectionalLight,
  DoubleSide,
  Geometry,
  Group,
  Material,
  MathUtils,
  Mesh,
  OrthographicCamera,
  PlaneBufferGeometry,
  Points,
  PointsMaterial,
  SphereBufferGeometry,
  Sprite,
  SpriteMaterial,
  Texture,
  Vector2,
  Vector3,
} from 'three';
import BaseThreeApp from '../BaseThreeApp';
import DebugInfo from '../DebugInfo';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

const worldCenter = new Vector3(0, 0, 0);
const frameMaterial = new THREE.MeshBasicMaterial({
  color: 0x111011,
  side: DoubleSide,
  depthTest: false,
  depthWrite: false,
  transparent: true,
  wireframe: true,
});

class _14SpirteTest extends BaseThreeApp {
  public scene: THREE.Scene;
  public camera: THREE.PerspectiveCamera;
  public sceneOrtho: THREE.Scene;
  public cameraOrtho: THREE.OrthographicCamera;
  public renderer: THREE.WebGLRenderer;
  public controller: OrbitControls;
  public geos: (Geometry | BufferGeometry)[] = [];
  public mats: Material[] = [];
  public texs: Texture[] = [];
  public clock: Clock;
  public group: THREE.Group;
  public mousePositon: Vector2 = new Vector2();

  start() {
    this.scene = new THREE.Scene();
    this.sceneOrtho = new THREE.Scene();
    const cw = this.clientWidth / 2;
    const ch = this.clientHeight / 2;
    this.camera = new THREE.PerspectiveCamera(60, cw / ch, 0.1, 10000);
    this.cameraOrtho = new OrthographicCamera(-cw, cw, ch, -ch, 0.1, 10000);
    this.resetCamera();

    const material = new THREE.MeshLambertMaterial({ color: 0x00ffff });
    this.mats.push(material);
    const buf = new SphereBufferGeometry(0.1);
    this.geos.push(buf);
    this.scene.add(new Mesh(buf, material));
    const axisHelp = new AxesHelper(5);
    this.geos.push(axisHelp.geometry);
    this.scene.add(axisHelp);
    this.controller = new OrbitControls(this.camera, this.element);
    this.controller.target.copy(worldCenter);
    this.controller.update();

    this.renderer = new THREE.WebGLRenderer({
      canvas: this.element,
    });
    this.renderer.setDrawingBufferSize(this.clientWidth, this.clientHeight, 1);

    const dirLight = new DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(1000, 1000, 1000);
    dirLight.target.position.set(0, 0, 0);
    this.scene.add(dirLight);

    const dirLight2 = new DirectionalLight(0xffffff, 0.3);
    dirLight2.position.set(-1000, -1000, -1000);
    this.scene.add(dirLight2);

    const ambient = new AmbientLight(0xffffff, 0.2);
    this.scene.add(ambient);

    const planeBuf = new PlaneBufferGeometry(2000, 2000, 250, 250);
    this.geos.push(planeBuf);
    const matPlane = new PointsMaterial({ color: 0x313333, size: 0.1 });
    const plane = new Points(planeBuf, matPlane);
    plane.rotation.x = MathUtils.degToRad(90);
    this.scene.add(plane);

    this.group = new THREE.Group();
    this.group.position.y = 50;
    this.scene.add(this.group);

    // this.spriteNode1();
    // this.spriteNodes2();
    this.spritePick();

    this.clock = new Clock(true);
    this.isRunning = true;
  }

  private spriteNode1(): void {
    const tex = new THREE.TextureLoader().load('images/dog.png');
    this.texs.push(tex);
    const spMaterial = new SpriteMaterial({ map: tex });
    const sp = new Sprite(spMaterial);
    sp.scale.set(300, 300, 1);
    this.scene.add(sp);
    requestAnimationFrame(this.render1.bind(this));
  }

  public render1(time: number) {
    if (!this.isRunning || this.element === null) {
      return;
    }
    DebugInfo.Instance.begin();
    this.renderer.clear(true, true, true);
    this.renderer.render(this.scene, this.camera);
    DebugInfo.Instance.end();
    requestAnimationFrame(this.render1.bind(this));
  }

  private spriteNodes2(): void {
    const amount = 200;
    const radius = 500;

    const textureLoader = new THREE.TextureLoader();

    const mapA = textureLoader.load('images/sprite0.png', createHUDSprites);
    const mapB = textureLoader.load('images/sprite1.png');
    const mapC = textureLoader.load('images/sprite2.png');

    const group = new THREE.Group();

    const materialC = new THREE.SpriteMaterial({
      map: mapC,
      color: 0xffffff,
      fog: true,
    });
    const materialB = new THREE.SpriteMaterial({
      map: mapB,
      color: 0xffffff,
      fog: true,
    });

    for (let a = 0; a < amount; a++) {
      const x = Math.random() - 0.5;
      const y = Math.random() - 0.5;
      const z = Math.random() - 0.5;

      let material: SpriteMaterial;

      if (z < 0) {
        material = materialB.clone();
      } else {
        material = materialC.clone();
        material.color.setHSL(0.5 * Math.random(), 0.75, 0.5);
        material.map.offset.set(-0.5, -0.5);
        material.map.repeat.set(2, 2);
      }

      const sprite = new THREE.Sprite(material);

      sprite.position.set(x, y, z);
      sprite.position.normalize();
      sprite.position.multiplyScalar(radius);

      group.add(sprite);
    }
    this.scene.add(group);

    const clientWidth = this.clientWidth / 2;
    const clientHeight = this.clientHeight / 2;
    const sceneOrtho = this.sceneOrtho;
    function createHUDSprites(texture) {
      const material = new THREE.SpriteMaterial({ map: texture });
      const width = texture.image.width;
      const height = texture.image.height;

      const spriteTL = new THREE.Sprite(material);
      spriteTL.center.set(0.0, 1.0);
      spriteTL.scale.set(width, height, 1);
      sceneOrtho.add(spriteTL);

      const spriteTR = new THREE.Sprite(material);
      spriteTR.center.set(1.0, 1.0);
      spriteTR.scale.set(width, height, 1);
      sceneOrtho.add(spriteTR);

      const spriteBL = new THREE.Sprite(material);
      spriteBL.center.set(0.0, 0.0);
      spriteBL.scale.set(width, height, 1);
      sceneOrtho.add(spriteBL);

      const spriteBR = new THREE.Sprite(material);
      spriteBR.center.set(1.0, 0.0);
      spriteBR.scale.set(width, height, 1);
      sceneOrtho.add(spriteBR);

      const spriteC = new THREE.Sprite(material);
      spriteC.center.set(0.5, 0.5);
      spriteC.scale.set(width, height, 1);
      sceneOrtho.add(spriteC);

      spriteTL.position.set(-clientWidth, clientHeight, 1); // top left
      spriteTR.position.set(clientWidth, clientHeight, 1); // top right
      spriteBL.position.set(-clientWidth, -clientHeight, 1); // bottom left
      spriteBR.position.set(clientWidth, -clientHeight, 1); // bottom right
      spriteC.position.set(0, 0, 1); // center
    }
    this.renderer.autoClear = false;
    requestAnimationFrame(this.render2.bind(this, group));
  }

  private render2(group: Group, time: number) {
    if (!this.isRunning || this.element === null) {
      return;
    }
    DebugInfo.Instance.begin();
    time = time / 1000;
    for (let i = 0, l = group.children.length; i < l; i++) {
      const sprite = group.children[i] as Sprite;
      const material = sprite.material;
      const scale = Math.sin(time + sprite.position.x * 0.01) * 0.3 + 1.0;

      let imageWidth = 1;
      let imageHeight = 1;

      if (material.map && material.map.image && material.map.image.width) {
        imageWidth = material.map.image.width;
        imageHeight = material.map.image.height;
      }

      sprite.material.rotation += 0.1 * (i / l);
      sprite.scale.set(scale * imageWidth, scale * imageHeight, 1.0);

      material.opacity = Math.sin(time + sprite.position.x * 0.01) * 0.4 + 0.6;
    }

    group.rotation.x = time * 0.5;
    group.rotation.y = time * 0.75;
    group.rotation.z = time * 1.0;

    this.renderer.clear();
    this.renderer.render(this.scene, this.camera);
    this.renderer.clearDepth();
    this.renderer.render(this.sceneOrtho, this.cameraOrtho);
    DebugInfo.Instance.end();
    requestAnimationFrame(this.render2.bind(this, group));
  }

  private spritePick() {
    const group = new THREE.Group();
    this.scene.add(group);

    const sprite1 = new THREE.Sprite(
      new THREE.SpriteMaterial({ color: '#f9f' })
    );
    sprite1.position.set(60, 150, 5);
    sprite1.scale.set(20, 50, 10);
    group.add(sprite1);

    const sprite2 = new THREE.Sprite(
      new THREE.SpriteMaterial({ color: '#6ff', sizeAttenuation: false })
    );
    sprite2.material.rotation = (Math.PI / 3) * 4;
    sprite2.position.set(12, -2, 2);
    sprite2.center.set(0.5, 0);
    sprite2.scale.set(0.1, 0.3, 0.1);
    group.add(sprite2);

    const group2 = new THREE.Object3D();
    group2.scale.set(10, 120, 10);
    group2.position.set(-5, 0, 0);
    group2.rotation.set(Math.PI, 0, 0);
    group.add(group2);

    const sprite3 = new THREE.Sprite(
      new THREE.SpriteMaterial({ color: '#69f' })
    );
    sprite3.position.set(-30, 2, 5);
    sprite3.scale.set(10, 2, 3);
    sprite3.center.set(-0.1, 0);
    group2.add(sprite3);
    requestAnimationFrame(this.renderPick.bind(this, group));
  }

  private selectedObject: Sprite = null;
  private readonly selectedColor: Color = new Color();
  private readonly raycaster = new THREE.Raycaster();

  private renderPick(group: Group, time: number) {
    if (!this.isRunning || this.element === null) {
      return;
    }
    DebugInfo.Instance.begin();
    if (this.selectedObject) {
      this.selectedObject.material.color.copy(this.selectedColor);
      this.selectedObject = null;
    }

    this.raycaster.setFromCamera(this.mousePositon, this.camera);
    const intersects = this.raycaster.intersectObject(group, true);

    if (intersects.length > 0) {
      const res = intersects.filter((res) => res && res.object)[0];
      if (res && res.object) {
        this.selectedObject = res.object as Sprite;
        this.selectedColor.copy(this.selectedObject.material.color);
        this.selectedObject.material.color.set('#f00');
      }
    }

    this.renderer.clear(true, true, true);
    this.renderer.render(this.scene, this.camera);
    DebugInfo.Instance.end();
    requestAnimationFrame(this.renderPick.bind(this, group));
  }

  public onmousemove(e: MouseEvent) {
    this.mousePositon.x = (e.offsetX / this.clientWidth) * 2 - 1;
    this.mousePositon.y = -(e.offsetY / this.clientHeight) * 2 + 1;
  }

  onresize(e: UIEvent) {
    super.onresize(e);
    console.log('onresive');
    if (this.renderer) {
      this.renderer.setDrawingBufferSize(
        this.clientWidth,
        this.clientHeight,
        1
      );
      if (this.camera) {
        const aspect = this.clientWidth / this.clientHeight;
        this.camera.aspect = aspect;
        this.camera.updateProjectionMatrix();

        const cw = this.clientWidth / 2;
        const ch = this.clientHeight / 2;
        this.cameraOrtho.left = -cw;
        this.cameraOrtho.right = cw;
        this.cameraOrtho.top = ch;
        this.cameraOrtho.bottom = -ch;
        this.cameraOrtho.updateProjectionMatrix();
      }
    }
  }

  protected resetCamera() {
    this.camera.position.set(500, 500, 500);
    this.camera.lookAt(worldCenter);

    this.cameraOrtho.position.set(0, 0, 50);
  }

  public onkeydown(e: KeyboardEvent): any | void {
    if (e.key === 'r' || ' ') {
      this.resetCamera();
    }
  }

  destory() {
    this.geos.forEach((v) => v.dispose());
    this.geos = [];
    this.mats.forEach((v) => v.dispose());
    this.mats = [];
    this.texs.forEach((v) => v.dispose());
    this.texs = [];
    super.destory();
  }
}

export default new _14SpirteTest();
